package com.jxpanda.spring.module.auth.core.token;

import com.jxpanda.spring.module.auth.core.authentication.token.UserDetailsAuthenticationToken;
import lombok.Builder;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import reactor.core.publisher.Mono;

import java.util.Optional;

@Builder
public class ReactiveOAuth2TokenService {

    private static final OAuth2RefreshToken EMPTY_REFRESH_TOKEN = new EmptyRefreshToken();

    private final ReactiveOAuth2TokenGenerator<? extends OAuth2Token> accessTokenGenerator;

    private final ReactiveOAuth2TokenGenerator<? extends OAuth2Token> refreshTokenGenerator;


    public ReactiveOAuth2TokenService(ReactiveOAuth2TokenGenerator<? extends OAuth2Token> accessTokenGenerator,
                                      @Nullable ReactiveOAuth2TokenGenerator<? extends OAuth2Token> refreshTokenGenerator) {
        this.accessTokenGenerator = accessTokenGenerator;
        this.refreshTokenGenerator = refreshTokenGenerator;
    }

    public Mono<OAuth2TokenPayload> generate(UserDetailsAuthenticationToken<?> userDetailsAuthenticationToken) {
        Mono<OAuth2AccessToken> accessToken = accessTokenGenerator.generate(userDetailsAuthenticationToken)
                .map(this::buildAccessToken);

        Mono<Optional<OAuth2RefreshToken>> refreshToken = Mono.justOrEmpty(refreshTokenGenerator)
                .flatMap(generator -> generator.generate(userDetailsAuthenticationToken)
                        .map(this::buildRefreshToken))
                .switchIfEmpty(Mono.just(Optional.empty()));


        return Mono.zip(accessToken, refreshToken)
                .map(tuple -> OAuth2TokenPayload.builder()
                        .accessToken(tuple.getT1())
                        .refreshToken(tuple.getT2().orElse(EMPTY_REFRESH_TOKEN))
                        .userDetails(userDetailsAuthenticationToken.getUserDetails())
                        .build());
    }

    private OAuth2AccessToken buildAccessToken(OAuth2Token oAuth2Token) {
        return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, oAuth2Token.getTokenValue(), oAuth2Token.getIssuedAt(), oAuth2Token.getExpiresAt());
    }

    private Optional<OAuth2RefreshToken> buildRefreshToken(OAuth2Token oAuth2Token) {
        return Optional.of(new OAuth2RefreshToken(oAuth2Token.getTokenValue(), oAuth2Token.getIssuedAt(), oAuth2Token.getExpiresAt()));
    }

}
